package cn.tools.knife4j.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;


/**
 * @author: e
 * @date: 2021/12/7
 * @description: 自动装配
 *
 * yml配置样例
 * knife4j-demo:
 *   swagger:
 *     enabled: true #是否开启文档
 *     title: #标题
 *     group:   #自定义组名
 *     description:  #描述
 *     version:  #版本
 *
 *
 *     docket:
 *       user:
 *         title: 用户模块
 *         base-package: cn.demo.controller #解析包路径，如：cn.demo.controller
 *         group: #自定义组名
 *         version: #版本
 *         contact:
 *           name:  #作者
 *           url:   #网址
 *           email:  #邮箱
 *
 *
 *       role:
 *         title: 权限模块
 *         base-package: cn.demo.contorller #解析包路径，如：cn.demo.controller
 *         group:  #自定义组名
 *         version:  #版本
 *
 *         contact:
 *           name:  #作者
 *           url:   #网址
 *           email:  #邮箱
 *
 *
 */
@Configuration
//指定启用SwaggerProperties类
@EnableConfigurationProperties(SwaggerProperties.class)
//启用swagger因为knife4j是依赖于swagger
@EnableSwagger2
//从yml文件中取值enabled，判断是true还是false来确定是否启用swagger
@ConditionalOnProperty(name="knife4j.swagger.enabled",havingValue = "true",matchIfMissing = true)
public class SwaggerAutoConfiguration implements BeanFactoryAware {

    @Resource
    SwaggerProperties swaggerProperties;
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Bean
    //条件注解，如果spring中已经存在这个类就不再执行这个方法继续产生类，用于防止多次调用
    @ConditionalOnMissingBean
    public List<Docket> createRestApi() {
        ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
        List<Docket> docketList = new LinkedList<>();

        // 调用方法判断，yml中是否有配置多个Docket参数
        if (swaggerProperties.getDocket().isEmpty()) {
            //如果没有设定分组，yml中就一个docket就直接用默认信息，给他添加进去
            Docket docket = createDocket(swaggerProperties);
            configurableBeanFactory.registerSingleton(swaggerProperties.getTitle(),docket);
            docketList.add(docket);
            return docketList;
        }

        //如果不止一个则获取出所有的docket进行配置
        Set<String> keySet = swaggerProperties.getDocket().keySet();
        for (String key:keySet) {
            //通过key值取出里面的value值
            SwaggerProperties.DocketInfo docketInfo = swaggerProperties.getDocket().get(key);

            //取出Docket配置的值构建一个apiInfo对象
            ApiInfo apiInfo = new ApiInfoBuilder()
                    //页面标题
                    .title(docketInfo.getTitle())
                    //创建人
                    .contact(new Contact(docketInfo.getContact().getName(),
                            docketInfo.getContact().getUrl(),
                            docketInfo.getContact().getEmail()))
                    //版本号
                    .version(docketInfo.getVersion())
                    //描述
                    .description(docketInfo.getDescription())
                    .build();

            // base-path处理
            // 当没有配置任何扫码路径path的时候，扫描所有/**
            if (docketInfo.getBasePath().isEmpty()) {
                docketInfo.getBasePath().add("/**");
            }
            List<Predicate<String>> basePath = new ArrayList<>();
            for (String path : docketInfo.getBasePath()) {
                basePath.add(PathSelectors.ant(path));
            }

            // 检测有没有要略过的路径，exclude-path处理
            List<Predicate<String>> excludePath = new ArrayList<>();
            for (String path : docketInfo.getExcludePath()) {
                excludePath.add(PathSelectors.ant(path));
            }

            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo)
                    .groupName(docketInfo.getGroup())
                    .select()
                    //为当前包路径
                    .apis(RequestHandlerSelectors.basePackage(docketInfo.getBasePackage()))
                    .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)),Predicates.or(basePath)))
                    .build();
            configurableBeanFactory.registerSingleton(key, docket);
            docketList.add(docket);
        }
        return docketList;

    }


    //构建 api文档的详细信息
    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
                //页面标题
                .title(swaggerProperties.getTitle())
                //创建人
                .contact(new Contact(swaggerProperties.getContact().getName(),
                        swaggerProperties.getContact().getUrl(),
                        swaggerProperties.getContact().getEmail()))
                //版本号
                .version(swaggerProperties.getVersion())
                //描述
                .description(swaggerProperties.getDescription())
                .build();
    }


    //创建接口文档对象
    private Docket createDocket(SwaggerProperties swaggerProperties) {
        //API 基础信息
        ApiInfo apiInfo = apiInfo(swaggerProperties);

        // base-path处理
        // 当没有配置任何path的时候，就扫描所有的包，找到接口文件
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add("/**");
        }
        //检测有没有指定需要排除的路径，如果没有配yml文件就为空
        List<Predicate<String>> basePath = new ArrayList<>();
        for (String path : swaggerProperties.getBasePath()) {
            basePath.add(PathSelectors.ant(path));
        }

        // exclude-path处理
        List<Predicate<String>> excludePath = new ArrayList<>();
        for (String path : swaggerProperties.getExcludePath()) {
            excludePath.add(PathSelectors.ant(path));
        }

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .groupName(swaggerProperties.getGroup())
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)),Predicates.or(basePath)))
                .build();
    }


}